package com.jintian.smart.kernel.orm.service;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

import cn.hutool.core.lang.Dict;
import com.jintian.smart.kernel.common.util.StringUtil;
import com.jintian.smart.kernel.core.annotation.orm.SimpleQuery;
import com.jintian.smart.kernel.core.domain.PageQuery;
import com.jintian.smart.kernel.core.expr.IExprCalcContext;
import com.jintian.smart.kernel.core.expr.IExpressionCalc;
import com.jintian.smart.kernel.module.Model;
import com.jintian.smart.kernel.orm.dto.*;
import com.jintian.smart.kernel.orm.enums.DmlAction;
import com.jintian.smart.kernel.orm.event.AfterEntityDmlEvent;
import com.jintian.smart.kernel.orm.event.BeforeEntityDmlEvent;
import com.jintian.smart.kernel.orm.mapper.internal.SimplePageQueryAMI;
import com.jintian.smart.kernel.orm.query.Query;
import org.apache.commons.beanutils.PropertyUtils;
import org.beetl.sql.clazz.ClassDesc;
import org.beetl.sql.clazz.NameConversion;
import org.beetl.sql.clazz.TableDesc;
import org.beetl.sql.clazz.kit.AutoSQLEnum;
import org.beetl.sql.core.SQLExecutor;
import org.beetl.sql.core.SQLManager;
import org.beetl.sql.core.SQLResult;
import org.beetl.sql.core.SQLSource;
import org.beetl.sql.core.SqlId;
import org.beetl.sql.core.page.DefaultPageRequest;
import org.beetl.sql.core.page.PageRequest;
import org.beetl.sql.core.page.PageResult;
import org.beetl.sql.core.query.LambdaQuery;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;

import com.fasterxml.jackson.databind.ObjectMapper;

public class EntityCommonService implements ApplicationContextAware {
    protected final SQLManager sqlManager;
    private final ObjectMapper objectMapper;
    private final IExpressionCalc IExpressionCalc;
    private ApplicationContext applicationContext;

    public EntityCommonService(SQLManager sqlManager, ObjectMapper objectMapper, IExpressionCalc expressionCalc) {
        this.sqlManager = sqlManager;
        this.objectMapper = objectMapper;
        this.IExpressionCalc = expressionCalc;
    }

    public SQLManager getSQLManager() {
        return this.sqlManager;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public Object getEntityById(String model, String path, String entity, String id) throws ClassNotFoundException {
        Class<?> clazz = this.getEntityClass(model, entity);
        return this.sqlManager.single(clazz, id);
    }

    @Transactional
    public Map<String, List<Object>> saveBatchChangeLog(String model, String path, List<Map<String, Object>> batch) throws ClassNotFoundException {
        Map<String, List<Object>> ret = new HashMap<>();
        for (Map<String, Object> item : batch) {
            String entityClass = (String) item.get("entityClass");
            @SuppressWarnings({"unchecked"}) Map<String, Object> request = (Map<String, Object>) item.get("changedData");
            ret.put(entityClass, this.saveChangeLog(model, path, entityClass, request));
        }
        return ret;
    }

    public PageResult<?> simpleQuery(String model, String path, String entity, Dict request) throws ClassNotFoundException {
        Class<?> entityClass = this.getEntityClass(model, entity);
        Class<?> conditionClass = null;
        SimpleQuery simpleQuery = entityClass.getAnnotation(SimpleQuery.class);
        if (simpleQuery != null) {
            conditionClass = Class.forName(simpleQuery.value(), true, entityClass.getClassLoader());
        }
        if (request.get("pageSize") == null || request.getInt("pageSize") == 0) {
            request.put("pageSize", 1000);
        }
        PageQuery<?, ?> page = PageQuery.valueOf(request, entityClass, conditionClass, objectMapper);
        PageRequest<?> pageRequest = page;
        if (pageRequest == null) {
            pageRequest = DefaultPageRequest.of(1, 10);
        }
        return SimplePageQueryAMI.execute(this.sqlManager.decideSqlManager(conditionClass), entityClass, pageRequest, page.getSimpleCondition());
    }

    @Transactional
    public List<Object> saveChangeLog(String model, String path, String entity, Map<String, Object> request) throws ClassNotFoundException {
        List<Object> changeLog = new ArrayList<>();
        Class<?> entityClass = this.getEntityClass(model, entity);
        ApplicationContext modelApplicationContext = this.getModelApplicationContext(model);
        EntityChangeLog<?> entityChangeLog = EntityChangeLog.valueOf(request, entityClass, objectMapper);
        if (entityChangeLog.getInsert() != null && entityChangeLog.getInsert().size() > 0) {
            BeforeEntityDmlEvent<?> beforeEvent = new BeforeEntityDmlEvent<>(path, DmlAction.INSERT, entityClass, entityChangeLog.getInsert());
            modelApplicationContext.publishEvent(beforeEvent);

            if (beforeEvent != null && beforeEvent.getTarget() != null && beforeEvent.getTarget().size() > 0) {
                this.sqlManager.decideSqlManager(entityClass).insertBatch(beforeEvent.getTarget());
                changeLog.addAll(entityChangeLog.getInsert());
            }
            AfterEntityDmlEvent<?> afterEvent = new AfterEntityDmlEvent<>(path, DmlAction.INSERT, entityClass, beforeEvent.getSource(), beforeEvent.getTarget());
            modelApplicationContext.publishEvent(afterEvent);

        }
        if (entityChangeLog.getUpdate() != null && entityChangeLog.getUpdate().size() > 0) {
            BeforeEntityDmlEvent<?> beforeEvent = new BeforeEntityDmlEvent<>(path, DmlAction.UPDATE, entityClass, entityChangeLog.getUpdate());
            modelApplicationContext.publishEvent(beforeEvent);

            if (beforeEvent != null && beforeEvent.getTarget() != null && beforeEvent.getTarget().size() > 0) {
                this.sqlManager.decideSqlManager(entityClass).updateByIdBatch(beforeEvent.getTarget());
                changeLog.addAll(entityChangeLog.getUpdate());
            }

            AfterEntityDmlEvent<?> afterEvent = new AfterEntityDmlEvent<>(path, DmlAction.UPDATE, entityClass, beforeEvent.getSource(), beforeEvent.getTarget());
            modelApplicationContext.publishEvent(afterEvent);
        }
        if (entityChangeLog.getTemplate() != null && entityChangeLog.getTemplate().size() > 0) {
            BeforeEntityDmlEvent<?> beforeEvent = new BeforeEntityDmlEvent<>(path, DmlAction.UPDATETEMPLATE, entityClass, entityChangeLog.getTemplate());
            modelApplicationContext.publishEvent(beforeEvent);

            if (beforeEvent != null && beforeEvent.getTarget() != null && beforeEvent.getTarget().size() > 0) {
                beforeEvent.getTarget().forEach(bean -> this.sqlManager.decideSqlManager(entityClass).updateTemplateById(bean));
                changeLog.addAll(entityChangeLog.getTemplate());
            }

            AfterEntityDmlEvent<?> afterEvent = new AfterEntityDmlEvent<>(path, DmlAction.UPDATETEMPLATE, entityClass, beforeEvent.getSource(), beforeEvent.getTarget());
            modelApplicationContext.publishEvent(afterEvent);
        }
        if (entityChangeLog.getDelete() != null && entityChangeLog.getDelete().size() > 0) {
            @SuppressWarnings("unchecked") List<? extends Serializable> delete = (List<? extends Serializable>) entityChangeLog.getDelete();
            this.cascadeDeleteByIds(modelApplicationContext, path, entityClass, null, delete, entityChangeLog.getCascades());
        }

        List<Object> keyParams = new ArrayList<>();
        if (changeLog.size() > 0) {
            ClassDesc classDesc = this.sqlManager.decideSqlManager(entityClass).getClassDesc(entityClass);
            for (Object bean : changeLog) {
                for (String attr : classDesc.getIdAttrs()) {
                    try {
                        keyParams.add(PropertyUtils.getProperty(bean, attr));
                    } catch (Exception e) {
                        throw new IllegalArgumentException(e);
                    }
                }
            }
            changeLog = new ArrayList<>(this.sqlManager.decideSqlManager(entityClass).selectByIds(entityClass, keyParams));
        }
        return changeLog;
    }

    public List<Object> batchCreate(String model, String path, String entity, Map<String, Object> request) throws ClassNotFoundException {
        Class<?> entityClass = this.getEntityClass(model, entity);
        CreateParams<?> createParams = CreateParams.valueOf(request, entityClass, objectMapper);
        List<Object> ret = new ArrayList<>();
        List<?> defaultValues = createParams.getDefaultValues();
        if (defaultValues == null) {
            defaultValues = Collections.emptyList();
        }
        int cnt = createParams.getBatchCount() > 0 ? createParams.getBatchCount() : defaultValues.size() > 0 ? defaultValues.size() : 1;
        int idx = 0;
        Object defaultValue = null;
        while (cnt-- > 0) {
            Object bean = this.create(model, entityClass, createParams.getContext());
            if (idx < defaultValues.size()) {
                defaultValue = defaultValues.get(idx);
            }
            idx++;
            if (defaultValue != null) {
                PropertyDescriptor[] props = BeanUtils.getPropertyDescriptors(bean.getClass());
                Set<String> nullProps = new HashSet<String>();
                for (PropertyDescriptor p : props) {
                    if (p.getReadMethod() != null) {
                        try {
                            if (p.getReadMethod().invoke(defaultValue) == null) {
                                nullProps.add(p.getName());
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
                BeanUtils.copyProperties(defaultValue, bean, nullProps.toArray(new String[0]));
            }
            ret.add(bean);
        }
        return ret;
    }

    private <T> T create(String model, Class<T> entityClass, Map<String, Object> params) {
        IExprCalcContext IExprCalcContext = null;
        try {
            IExprCalcContext = this.getModelApplicationContext(model).getBean(IExprCalcContext.class);
        } catch (Exception ex) {
        }
        return IService.create(this.sqlManager.decideSqlManager(entityClass), entityClass, this.IExpressionCalc, IExprCalcContext, params);
    }

    @Transactional
    public boolean delete(String model, String path, String entity, Map<String, Object> request) throws ClassNotFoundException {
        Class<?> entityClass = this.getEntityClass(model, entity);
        DeleteParams<?> deleteParams = DeleteParams.valueOf(request, entityClass, this.objectMapper);
        @SuppressWarnings("unchecked") List<? extends Serializable> pkList = (List<? extends Serializable>) deleteParams.getRows();
        this.cascadeDeleteByIds(this.getModelApplicationContext(model), path, entityClass, null, pkList, deleteParams.getCascades());
        return true;
    }

    /**
     * 级联删除
     *
     * @param entityClass
     * @param entityForeignKeys
     * @param beans
     * @param cascades
     */
    private void cascadeDeleteByIds(ApplicationContext modelContext, String path, Class<?> entityClass, List<String> entityForeignKeys, List<? extends Serializable> beans, List<Cascade> cascades) {
        if (cascades != null && cascades.size() > 0) {
            for (Serializable bean : beans) {
                // 加载下级级联数据
                Map<String, List<Serializable>> cascadeMap = this.cascadeLoad(entityClass, bean, cascades, false);
                for (Cascade cascade : cascades) {
                    Class<?> targetClass = null;
                    try {
                        targetClass = Class.forName(cascade.getTarget(), true, entityClass.getClassLoader());
                    } catch (ClassNotFoundException e1) {
                        throw new IllegalArgumentException(e1);
                    }
                    String[] foreignKeys = cascade.getForeignKey().split(";");
                    List<String> foreignAttrs = new ArrayList<>();
                    for (String foreignKey : foreignKeys) {
                        String[] args = foreignKey.split("=");
                        foreignAttrs.add(args[0].trim());
                    }
                    List<Serializable> cascadeData = cascadeMap.get(cascade.getTarget());
                    if (cascadeData == null || cascadeData.size() == 0) continue;
                    if (Cascade.DELETE.equals(cascade.getKind()) || StringUtil.isBlank(cascade.getKind())) {
                        this.cascadeDeleteByIds(modelContext, path, targetClass, foreignAttrs, cascadeData, cascade.getCascades());
                    } else {
                        this.cascadeSetNullByIds(modelContext, path, targetClass, foreignAttrs, cascadeData, cascade.getCascades());
                    }
                }
            }
        }

        BeforeEntityDmlEvent<?> beforeEvent = new BeforeEntityDmlEvent<>(path, DmlAction.DELETE, entityClass, beans);
        modelContext.publishEvent(beforeEvent);

        if (beforeEvent != null && beforeEvent.getTarget() != null && beforeEvent.getTarget().size() > 0) {
            this.sqlManager.decideSqlManager(entityClass).deleteByIds(beforeEvent.getBeanClass(), beforeEvent.getTarget());
        }

        AfterEntityDmlEvent<?> afterEvent = new AfterEntityDmlEvent<>(path, DmlAction.DELETE, beforeEvent.getBeanClass(), beforeEvent.getSource(), beforeEvent.getTarget());
        modelContext.publishEvent(afterEvent);
    }

    /**
     * 级联设置为空
     *
     * @param entityClass
     * @param entityForeignKeys
     * @param beans
     * @param cascades
     */
    private void cascadeSetNullByIds(ApplicationContext modelContext, String path, Class<?> entityClass, List<String> entityForeignKeys, List<Serializable> beans, List<Cascade> cascades) {
        NameConversion nameConversion = this.sqlManager.decideSqlManager(entityClass).getNc();
        String tableName = nameConversion.getTableName(entityClass);
        TableDesc table = this.sqlManager.decideSqlManager(entityClass).getMetaDataManager().getTable(tableName);
        ClassDesc classDesc = table.genClassDesc(entityClass, nameConversion);

        BeforeEntityDmlEvent<?> beforeEvent = new BeforeEntityDmlEvent<>(path, DmlAction.UPDATETEMPLATE, "setForeignKeyNull(" + StringUtil.join(entityForeignKeys, ",") + ")", entityClass, beans);
        modelContext.publishEvent(beforeEvent);

        for (Serializable bean : beans) {
            // 加载下级级联数据
            if (cascades != null && cascades.size() > 0) {
                Map<String, List<Serializable>> cascadeMap = this.cascadeLoad(entityClass, bean, cascades, false);
                for (Cascade cascade : cascades) {
                    Class<?> targetClass = null;
                    try {
                        targetClass = Class.forName(cascade.getTarget(), true, entityClass.getClassLoader());
                    } catch (ClassNotFoundException e1) {
                        throw new IllegalArgumentException(e1);
                    }
                    String[] foreignKeys = cascade.getForeignKey().split(";");
                    List<String> foreignAttrs = new ArrayList<>();
                    for (String foreignKey : foreignKeys) {
                        String[] args = foreignKey.split("=");
                        foreignAttrs.add(args[0].trim());
                    }

                    List<Serializable> cascadeData = cascadeMap.get(cascade.getTarget());
                    if (cascadeData == null || cascadeData.size() == 0) continue;
                    if (Cascade.DELETE.equals(cascade.getKind()) || StringUtil.isBlank(cascade.getKind())) {
                        this.cascadeDeleteByIds(modelContext, path, targetClass, foreignAttrs, cascadeData, cascade.getCascades());
                    } else {
                        this.cascadeSetNullByIds(modelContext, path, targetClass, foreignAttrs, cascadeData, cascade.getCascades());
                    }
                }
            }
            LambdaQuery<?> query = this.sqlManager.decideSqlManager(entityClass).lambdaQuery(entityClass);
            entityForeignKeys.forEach(fk -> {
                query.set(fk, null);
            });
            Iterator<String> cols = classDesc.getInCols().iterator();
            Iterator<String> properties = classDesc.getAttrs().iterator();
            while (cols.hasNext() && properties.hasNext()) {
                String col = cols.next();
                String prop = properties.next();
                try {
                    query.andEq(col, PropertyUtils.getProperty(bean, prop));
                } catch (Exception e) {
                    throw new IllegalArgumentException(e);
                }
            }
            query.update();
        }

        AfterEntityDmlEvent<?> afterEvent = new AfterEntityDmlEvent<>(path, DmlAction.UPDATETEMPLATE, "setForeignKeyNull(" + StringUtil.join(entityForeignKeys, ",") + ")", entityClass, beforeEvent.getSource(), beforeEvent.getTarget());
        modelContext.publishEvent(afterEvent);
    }

    /**
     * 级联加载
     *
     * @param entityClass
     * @param id
     * @param cascades
     * @param recursion
     * @return
     */
    private Map<String, List<Serializable>> cascadeLoad(Class<?> entityClass, Serializable id, List<Cascade> cascades, boolean recursion) {
        Map<String, List<Serializable>> ret = new HashMap<>();
        String idAttr = this.sqlManager.decideSqlManager(entityClass).getClassDesc(entityClass).getIdAttr();
        Serializable bean = entityClass.isAssignableFrom(id.getClass()) ? id : (Serializable) this.sqlManager.decideSqlManager(entityClass).unique(entityClass, id);
        for (Cascade cascade : cascades) {
            Class<?> targetClass = null;
            try {
                targetClass = Class.forName(cascade.getTarget(), true, entityClass.getClassLoader());
            } catch (ClassNotFoundException e1) {
                throw new IllegalArgumentException(e1);
            }
            Map<String, Object> subLoadVariables = new HashMap<>();

            String[] foreignKeys = cascade.getForeignKey().split(";");
            for (String foreignKey : foreignKeys) {
                if (StringUtil.isBlank(foreignKey)) continue;
                Object parentValue = null;
                String[] keys = foreignKey.split("=");
                String parentAttr = keys.length > 1 ? keys[1].trim() : idAttr;
                String subAttr = keys[0].trim();
                try {
                    parentValue = PropertyUtils.getProperty(bean, parentAttr);
                } catch (Exception e) {
                    throw new IllegalArgumentException(e);
                }
                subLoadVariables.put(subAttr, parentValue);
            }
            SQLExecutor script = this.sqlManager.decideSqlManager(entityClass).getScript(targetClass, AutoSQLEnum.SELECT_BY_TEMPLATE);
            @SuppressWarnings("unchecked") List<Serializable> cascadeData = (List<Serializable>) script.select(targetClass, subLoadVariables);
            List<Serializable> exists = ret.get(targetClass.getName());
            if (exists != null) {
                exists.addAll(cascadeData);
            } else {
                ret.put(targetClass.getName(), cascadeData);
            }
            if (recursion && cascade.getCascades() != null && cascade.getCascades().size() > 0) {
                for (Serializable obj : cascadeData) {
                    Map<String, List<Serializable>> subMap = this.cascadeLoad(targetClass, obj, cascade.getCascades(), recursion);
                    for (Entry<String, List<Serializable>> e : ret.entrySet()) {
                        List<Serializable> exitsList = subMap.get(e.getKey());
                        if (exitsList == null) {
                            subMap.put(e.getKey(), e.getValue());
                        } else {
                            exitsList.addAll(e.getValue());
                        }
                    }
                }
            }
        }
        return ret;
    }

    @SuppressWarnings("unchecked")
    public PageResult<?> viewQuery(String model, String path, String entity, @RequestBody Query query) throws Exception {
        String viewId = query.getTables()[0];
        int dotIdx = viewId.lastIndexOf(".");
        String namespace = viewId.substring(0, dotIdx);
        String id = viewId.substring(dotIdx + 1);
        SqlId sqlId = SqlId.of(namespace, id);
        SQLSource source = this.sqlManager.getSqlLoader().querySQL(sqlId);
        if (source == null) {
            throw this.sqlManager.getSqlLoader().getException(sqlId);
        }
        String viewSQL = source.getTemplate();
        StringBuffer sql = new StringBuffer("select ");
        if (query.getColumns() != null && query.getColumns().length > 0) {
            sql.append(StringUtil.join(query.getColumns(), ","));
        } else {
            sql.append("*");
        }
        sql.append(" from (\n").append(viewSQL).append("\n)");
        Map<String, Object> paras = new HashMap<>();
        if (query.getConditions() != null && query.getConditions().length > 0) {
            int conditionIndex = 0;
            IExprCalcContext defaultContext = this.getModelApplicationContext(model).getBean(IExprCalcContext.class);
            Map<String, Object> variables = defaultContext != null ? defaultContext.getDefaultContextVariables() : null;
            for (Condition condition : query.getConditions()) {
                if (condition.getValue() == null) continue;
                condition.setId("param" + (conditionIndex++));
                for (int idx = 0, len = condition.getValue().length; idx < len; idx++) {
                    Object value = condition.getValue()[idx];
                    if (value instanceof String && ((String) value).contains("(")) {
                        value = this.IExpressionCalc.calculate((String) value, variables);
                        condition.getValue()[idx] = value;
                    }
                }
            }
            String filter = Query.Sql.build(query);
            if (filter != null) {
                sql.append("\n").append("where ").append(filter);
                for (Condition condition : query.getConditions()) {
                    if (condition.getValue() == null) continue;
                    for (int idx = 0, len = condition.getValue().length; idx < len; idx++) {
                        Object value = condition.getValue()[idx];
                        paras.put(condition.getId() + "_" + idx, value);
                    }
                }
            }
        }
        if (StringUtil.isNoneBlank(query.getOrderBy())) {
            sql.append("\n").append(query.getOrderBy());
        }
        org.beetl.sql.core.page.PageRequest<?> pageRequest;
        if (query.getPageNo() != null) {
            pageRequest = DefaultPageRequest.of(query.getPageNo(), query.getPageSize());
        } else {
            pageRequest = DefaultPageRequest.of(1, 0, query.getTrackTotalHits() > 0);
        }
        String template = sql.toString();// PageQueryFuntion
        if (pageRequest.isTotalRequired()) {
            template = "select #{page('*')} from (\n" + template + "\n)";
        }
        @SuppressWarnings("rawtypes") Class entityClass = StringUtil.isNoneBlank(entity) ? this.getEntityClass(model, entity) : Map.class;
        return this.sqlManager.executePageQuery(template, entityClass, paras, pageRequest);
    }

    public PageResult<?> viewSimpleQuery(String model, String path, String viewId, String simpleQuery, String entity, Dict request) throws Exception {
        Class<?> entityClass = StringUtil.isNoneBlank(entity) ? this.getEntityClass(model, entity) : Map.class;
        Class<?> conditionClass = null;
        if (StringUtil.isNoneBlank(simpleQuery)) {
            conditionClass = Class.forName(simpleQuery, true, entityClass.getClassLoader());
        } else {
            SimpleQuery anno = entityClass.getAnnotation(SimpleQuery.class);
            if (anno != null) {
                conditionClass = Class.forName(anno.value(), true, entityClass.getClassLoader());
            }
        }
        if (request.get("pageSize") == null || request.getInt("pageSize") == 0) {
            request.put("pageSize", 1000);
        }
        PageQuery<?, ?> page = PageQuery.valueOf(request, Map.class, conditionClass, objectMapper);
        org.beetl.sql.core.page.PageRequest<?> pageRequest = page;
        if (pageRequest == null) {
            pageRequest = DefaultPageRequest.of(1, 10);
        }

        int dotIdx = viewId.lastIndexOf(".");
        String namespace = viewId.substring(0, dotIdx);
        String id = viewId.substring(dotIdx + 1);
        SqlId sqlId = SqlId.of(namespace, id);
        // 编译sqlId
        SQLResult sqlResult = this.sqlManager.decideSqlManager(entityClass).getSQLResult(sqlId, page);
        List<Object> params = sqlResult.jdbcPara.stream().map((p) -> p.value).collect(Collectors.toList());
        PageResult<?> ret = SimplePageQueryAMI.execute(this.sqlManager.decideSqlManager(entityClass), entityClass, "(" + sqlResult.jdbcSql + ") tmp ", pageRequest, page.getSimpleCondition(), params);
        return ret;
    }

    private ApplicationContext getModelApplicationContext(String model) {
        if (StringUtil.isNotEmpty(model)) {
            return this.applicationContext.getBean("model$" + model, Model.class).getModelContext();
        } else {
            return this.applicationContext;
        }
    }

    private ClassLoader getModelClassLoader(String model) {
        if (StringUtil.isNotEmpty(model)) {
            return this.applicationContext.getBean("model$" + model, Model.class).getClass().getClassLoader();
        } else {
            return this.applicationContext.getClassLoader();
        }
    }

    private Class<?> getEntityClass(String model, String entity) throws ClassNotFoundException {
        return Class.forName(entity, true, this.getModelClassLoader(model));
    }
}
